/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://git.oschina.net/cblock/embedme
 * Copyright 2014~2020 @ ShenZhen ,China
*******************************************************************************/
#include "Logger.h"
#include "Tracer.h"
#include "FileUtil.h"
#include "StrUtil.h"
#include "JSONData.h"
#include "RegExp.h"
#include "ProcUtil.h"

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>
#include <list>

#define LOG_LENGTH_MAX		    4096		          /* 每条日志最大长度,4096Bytes */
#define LOG_STORE_DEFAULT       8192                  /* 默认最大可用存储空间--8096M */
#define LOG_PATH_DEFAULT        "./log/"              /* 默认在当前工作目录下log文件夹 */

#define LOG_FILENAME_FORMAT     "%s_%08d.log"         /* 日志文件名格式 */
#define LOG_FILENAME_PATTERN    "%s_[0-9]{8}.log"     /* 日志文件名匹配规则 */

#define LOG_MAIN_FILE           "logmain.json"        /* 日志维护文件 */
#define LOG_NAME_DEFAULT        "log"                 /* 默认日志文件名 */
#define LOGFILE_NUM_DEFAULT     10000                 /* 默认日志数量:10000 */
#define LOGFILE_SIZE_DEFAULT    10                    /* 默认日志文件大小:10M */

#define DATALOG_NAME_DEFAULT    "data"
namespace libemb{
/**
 *  \brief  Logger构造函数
 *  \param  none
 *  \return none
 */
Logger::Logger():
m_initialized(false)
{
    m_logPath = LOG_PATH_DEFAULT;
    m_logName = LOG_NAME_DEFAULT;
    m_logNumberMax = LOGFILE_NUM_DEFAULT;
    m_logStoreMax = LOG_STORE_DEFAULT;
    m_fileSizeMax = LOGFILE_SIZE_DEFAULT; 
}
/**
 *  \brief  Logger析构函数
 *  \param  none
 *  \return none
 */
Logger::~Logger()
{
}

bool Logger::init(std::string logPath,std::string logName,int logNumberMax,int logStoreMax, int fileSizeMax)
{
    if (!logPath.empty()) 
    {
        m_logPath = logPath; 
    }

    if (!logName.empty()) 
    {
        m_logName = logName;
    }

    if (logNumberMax>0)
    {
        m_logNumberMax = logNumberMax;
    }

    if (logStoreMax>0) 
    {
        m_logStoreMax = logStoreMax;
    }

    if (fileSizeMax>0) 
    {
        m_fileSizeMax = fileSizeMax;
    }
    initialize();
    return true;
}

/**
 *  \brief  记录日志
 *  \param  fmt 参数
 *  \return void
 *  \note   none
 */
void Logger::log(const char * fmt, ...)
{
    va_list argp;
    va_start(argp, fmt);
    makeLog(fmt, argp);
    va_end(argp);
}

bool Logger::getLogInfo(LogInfo_S* logInfo)
{
    if (logInfo==NULL) 
    {
        return false;
    }
    logInfo->m_logPath = m_logPath;
    logInfo->m_logName = m_logName;
    logInfo->m_logSize = Directory::getContentSize(m_logPath.c_str());
    return true;
}

void Logger::initialize()
{
    if (!m_initialized)
    {
        /* 更新维护文件,更新m_currentId的值,并更新最新日志时间 */
        if (!updateMaintainFile())
        {
            return;
        }
        /* 先清理日志文件(把不符合当前配置的日志文件删除) */
        if (!cleanLogFile()) 
        {
            TRACE_ERR_CLASS("clean log file failed.\n");
            return;
        }
        /* 检查日志容量,如果容量超出可允许范围,则删除部分旧日志 */
        if (!cleanLogStore())
        {
            TRACE_ERR_CLASS("clean log store failed.\n");
            return;
        }

        /* 打开新日志 */
        openNewLogFile(); 

        m_mainThread.start(this);
        m_initialized = true;
    }
}

void Logger::makeLog(const char * format, va_list argp)
{
    initialize();
    AutoLock lock(&m_logBufferLock);
    char buf[LOG_LENGTH_MAX] = {0};
    struct tm * ptm;
    time_t timep;
    time(&timep);
    ptm = localtime(&timep);
    std::string header = StrUtil::stringFormat("[%d%02d%02d %02d:%02d:%02d]:", \
                                             ptm->tm_year + 1900, ptm->tm_mon + 1, \
                                             ptm->tm_mday, ptm->tm_hour, \
                                             ptm->tm_min, ptm->tm_sec);
    memcpy(buf, header.c_str(), header.size());
    int rest = LOG_LENGTH_MAX-header.size()-1;/* 剩余空间 */
    int nsize = vsnprintf(buf + header.size(),rest, format, argp);
    if (nsize>0) 
    {
        if (nsize>=rest) {
            buf[LOG_LENGTH_MAX-5]='.';
            buf[LOG_LENGTH_MAX-4]='.';
            buf[LOG_LENGTH_MAX-3]='.';
            buf[LOG_LENGTH_MAX-2]='\n';
            buf[LOG_LENGTH_MAX-1]=0;
            nsize = rest;
        }
        std::string logText = std::string(buf, header.size() + nsize);
        m_logBuffer.push_back(logText);
    }
}

bool Logger::updateMaintainFile()
{
    /* 获取当前时间 */
    time_t timep;
    struct tm * ptm = NULL;
    time(&timep);
    ptm = localtime(&timep);
    std::string timeString = StrUtil::stringFormat("%d%02d%02d%02d%02d%02d", \
                                                 ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, \
                                                 ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    /* 如果没有维护文件则创建,有则更新当前currentId和时间 */
    int currentId = -1;
    std::string mainFilePath = m_logPath+"/";
    mainFilePath.append(m_logName);
    mainFilePath.append("/");
    if (!Directory::isExist(mainFilePath.c_str())) 
    {
        if(!Directory::createDir(mainFilePath.c_str(),true))
        {
            TRACE_ERR_CLASS("create directory failed: %s\n",mainFilePath.c_str());
            return false;
        }
    }
    mainFilePath.append(LOG_MAIN_FILE);
    if (File::isExist(mainFilePath.c_str())) 
    {
        JSONData mainData;
        JSONNode* rootNode = mainData.initWithContentOfFile(mainFilePath.c_str());
        if (rootNode==NULL) 
        {
            TRACE_ERR_CLASS("can't open file:%s.\n",mainFilePath.c_str());
            return false;
        }
        currentId = (*rootNode)["currentId"].toInt();
    }
    JSONData logMainData;
    JSONNode* rootNode = logMainData.initWithObjectFormat();
    if (rootNode==NULL) 
    {
        TRACE_ERR_CLASS("can't create json node.\n");
        return false;
    }
    rootNode->addSubNode(m_logPath.c_str(),"logPath");
    rootNode->addSubNode(m_logName.c_str(),"logName");
    rootNode->addSubNode(timeString.c_str(),"logTime");
    currentId = (currentId + 1) % m_logNumberMax; 
    rootNode->addSubNode(currentId, "currentId"); 
    logMainData.saveAsFile(mainFilePath.c_str(),true);
    m_currentId = currentId;
    return true;
}


bool Logger::cleanLogFile()
{
    int maxLogFileId = m_logNumberMax-1;
    std::string logFullPath = m_logPath + "/";
    logFullPath += m_logName;
    /* 如果日志路径不存在，则创建日志目录 */
    if (!Directory::isExist(logFullPath.c_str()))
    {
        if (!Directory::createDir(logFullPath.c_str(),true))
        {
            TRACE_ERR_CLASS("create dir:[%s] error:%s.\n", logFullPath.c_str(), ERROR_STRING);
            return false;
        }
        return true;
    }

    /* 进入日志目录 */
    Directory directory;
    if(!directory.enter(logFullPath))
    {
        return false;
    }
    /* 扫描目录，删除不属于当前配置的文件 */
    std::vector<std::string> logFileList = directory.listAll();
    int fileNum = logFileList.size();
    for (int i=0; i<fileNum; i++) 
    {
        std::string fileName = logFileList[i]; 
        if (fileName == "."  ||
            fileName == ".." ||
            fileName == LOG_MAIN_FILE) /* 不包含维护文件 */
        {
            continue;
        }
        
        /* 文件名格式:xxxx_dddddddd.log,删除不符合格式的文件 */
        RegExp reg;
        std::string pattern = StrUtil::stringFormat(LOG_FILENAME_PATTERN, m_logName.c_str()); 
        std::string maxFileName = StrUtil::stringFormat(LOG_FILENAME_FORMAT, m_logName.c_str(), maxLogFileId);        
        if(!(reg.match(pattern,fileName)) || !(fileName<=maxFileName))
        {
            std::string logFullName = m_logPath + "/";
            logFullName.append(m_logName);
            logFullName.append("/");
            logFullName.append(fileName);
            File::deleteFile(logFullName.c_str());
            TRACE_DBG_CLASS("clean invalid logfile:%s\n",logFullName.c_str());
        }
        else
        {
            //TRACE_DBG_CLASS("matched log file:%s.\n",fileName.c_str());
        }

    }
    return true;
}

bool Logger::cleanLogStore()
{
    std::string logFullPath = m_logPath + "/";
    logFullPath += m_logName;
    /* 如果日志路径不存在，则创建日志目录 */
    if (!Directory::isExist(logFullPath.c_str()))
    {
        if (!Directory::createDir(logFullPath.c_str(),true))
        {
            TRACE_ERR_CLASS("create dir:[%s] error:%s.\n", logFullPath.c_str(), ERROR_STRING);
            return false;
        }
        return true;
    }

    /* 计算剩余容量,当小于1/8的时候将会删除最旧的日志 */
    int storeSize = Directory::getContentSize(logFullPath.c_str());
    int freeSize = (m_logStoreMax<<20) - storeSize;
    if (freeSize>((m_logStoreMax<<20)>>3))
    {
        return true;
    }
    else
    {
        //TRACE_ERR_CLASS("%s store max:%d, store now:%d\n",logFullPath.c_str(),m_logStoreMax,storeSize);
    }

    /* 进入日志目录 */
    Directory directory;
    if(!directory.enter(logFullPath))
    {
        return false;
    }
    /* 扫描目录，删除八分之一数量的文件 */
    std::vector<std::string> logFileList = directory.listAll();
    int fileNum = logFileList.size()>>3;
    int logFileId = (m_currentId + 1) % m_logNumberMax; 
    for (int i=0; i<fileNum; ) 
    {
        /* 文件名格式:xxxx_dddddddd.log,删除从m_currentId后的文件(最老的) */
        std::string fileName = StrUtil::stringFormat(LOG_FILENAME_FORMAT, m_logName.c_str(), logFileId); 
        std::string logFullName = m_logPath + "/";
        logFullName.append(m_logName);
        logFullName.append("/");
        logFullName.append(fileName);
        if (File::isExist(logFullName.c_str())) 
        {
            File::deleteFile(logFullName.c_str());
            TRACE_DBG_CLASS("delete old logfile:%s\n",logFullName.c_str());
            i++;
        }
        logFileId = (logFileId+1) % m_logNumberMax; 
    }
    return true;
}

/* 重新创建日志文件 */
bool Logger::openNewLogFile()
{
    /* 先关闭日志文件 */
    m_logFile.close();

    std::string logFileFullPath = m_logPath+"/";
    logFileFullPath.append(m_logName);
    logFileFullPath.append("/");

    /* 没有该目录则创建 */
    if (!Directory::isExist(logFileFullPath.c_str())) 
    {
        if(!Directory::createDir(logFileFullPath.c_str(),true))
        {
            TRACE_ERR_CLASS("create directory failed: %s\n",logFileFullPath.c_str());
            return false;
        }
    }

    /* 重新创建文件 */
    logFileFullPath.append(StrUtil::stringFormat(LOG_FILENAME_FORMAT, m_logName.c_str(), m_currentId));
    if(!m_logFile.open(logFileFullPath.c_str(), IO_MODE_REWR_ORNEW))
    {
        TRACE_ERR_CLASS("can't open logfile:%s\n",logFileFullPath.c_str());
        return false;
    }
    return true;
}

void Logger::run()
{
    bool opened = false;
    while (!opened) 
    {
        opened = openNewLogFile();
        Thread::msleep(1000);
    }
    while (1)
    {
        m_logBufferLock.lock();
        if (m_logBuffer.size()>0)
        {
            std::vector<std::string>::iterator iter = m_logBuffer.begin();
            std::string logText = *iter;
            {
                if (m_logFile.getSize() >= (m_fileSizeMax<<20)) 
                {
                    updateMaintainFile();/* 更新id */
                    cleanLogStore();     /* 检查当前文件夹是否超出允许容量范围,超出则清理 */
                    openNewLogFile();    /* 重新打开日志文件 */
                }
                m_logFile.writeData(logText.c_str(), logText.size());
            }
            m_logBuffer.erase(iter);
        }
        m_logBufferLock.unLock();
        Thread::msleep(10);
    }
}



LoggerManager::LoggerManager()
{
}

LoggerManager::~LoggerManager()
{
}
/*******************************************************************************
 * 配置文件样例:
 * appLog = 
 * ({
 *      logPath="/data/log";  //日志根目录
 *      logName="core";       //日志文件目录名称
 *      logNumberMax=10000;   //日志文件最大数目，到达最大值后会循环覆盖
 *      logStoreMax=1024;     //日志最大可用存储空间(MBytes),到达最大数量后会循环覆盖
 *      fileSizeMax=1;        //日志文件最大大小
 *  });
 *  
 ********************************************************************************/
bool LoggerManager::initWithSettings(Settings settings)
{
    m_loggerMap.clear();
    /* 判断列表大小(如果不是列表将返回0) */
    int logNum = settings.size(); 
    if (logNum==0) 
    {
        TRACE_ERR_CLASS("Settings is not a list.\n");
        return false;
    }
    for (int i=0; i<logNum; i++) 
    {
        std::string logPath = settings[i]["logPath"].toString();
        if (logPath.empty())
        {
            logPath = LOG_PATH_DEFAULT; 
        }

        /* 获取主日志名称 */
        std::string logName = settings[i]["logName"].toString();
        if (logName.empty())
        {
            logName = LOG_NAME_DEFAULT; 
        }

        int logNumberMax = settings[i]["logNumberMax"].toInt();
        if (logNumberMax<=0)
        {
            logNumberMax = LOGFILE_NUM_DEFAULT; 
        }

        int logStoreMax = settings[i]["logStoreMax"].toInt();
        if (logStoreMax<=0)
        {
            logStoreMax = LOG_STORE_DEFAULT; 
        }

        int fileSizeMax = settings[i]["fileSizeMax"].toInt();
        if (fileSizeMax<=0)
        {
            fileSizeMax = LOGFILE_SIZE_DEFAULT; 
        }

        LoggerMap::iterator iter = m_loggerMap.find(logName); 
    	if(iter!=m_loggerMap.end())
    	{
            TRACE_ERR_CLASS("There is a same logger:%s allready here!\n",logName.c_str());
    		continue;
    	}

        Logger* logger = NEW_OBJ Logger();
        if(!logger->init(logPath, logName, logNumberMax, logStoreMax, fileSizeMax))
        {
            DEL_OBJ(logger);
            continue;
        }
    	//m_loggerMap.insert(std::make_pair<std::string, Logger*>(logName, logger));
    	m_loggerMap.insert(std::make_pair(logName, logger));
    }
    return true;
}

Logger* LoggerManager::getLogger(std::string logName)
{
    LoggerMap::iterator iter = m_loggerMap.find(logName); 
    if(iter!=m_loggerMap.end())
    {
        return iter->second;
    }
    return NULL;
}


bool LoggerManager::exportLog(const std::string destPath,const std::string logName)
{
    if (destPath.empty()) 
    {
        TRACE_ERR_CLASS("dst path is empty!\n");
        return false;
    }

    std::string cmd="";
    Logger* logger=NULL;
    if (logName.empty()) 
    {
        LoggerMap::iterator iter;
        for (iter=m_loggerMap.begin(); iter!=m_loggerMap.end();iter++) 
        {
            logger = iter->second;
            if (logger!=NULL) 
            {
                LogInfo_S logInfo;
                if(logger->getLogInfo(&logInfo))
                {
                    std::string logFullPath = logInfo.m_logPath+"/"+logInfo.m_logName;
                    if(Directory::isExist(logFullPath.c_str()))
                    {
                        cmd = "cp -pR ";
                        cmd += logFullPath;
                        cmd += " ";
                        cmd += destPath;
                        cmd += ";";
                    }
                }
            }
        }
    }
    else
    {
        logger = getLogger(logName);
        if (logger!=NULL) 
        {
            LogInfo_S logInfo;
            if(logger->getLogInfo(&logInfo))
            {
                std::string logFullPath = logInfo.m_logPath+"/"+logInfo.m_logName;
                if(Directory::isExist(logFullPath.c_str()))
                {
                    cmd = "cp -pR ";
                    cmd += logFullPath;
                    cmd += " ";
                    cmd += destPath;
                    cmd += ";";
                }
            }
        }

    }
    if (!cmd.empty()) 
    {
        cmd += "sync;sync;sync;sleep 1;";
        ProcUtil::execute(cmd);
        return true;
    }
    return false;
}
}
